package xman.mssh.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.HttpVersion;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HTTPLongClient {
    
    private static final Logger logger = LoggerFactory.getLogger(HTTPLongClient.class);
    
    private static final long PERIOD_TIME = 1000L * 3600;
    
    private static HTTPLongClient instance = null;
    
    private HttpClient httpClient = null;
    
    private int connectTimeout = 1000;
    
    private int soTimeout = 20000;
    
    private int maxHostConnections = 100;
    
    private int maxTotalConnections = 1000;
    
    private long lastClearTime = System.currentTimeMillis();

    static {
        instance = newInstance();
    }
    
    public static HTTPLongClient newInstance() {
        return new HTTPLongClient();
    }
    
    public static HTTPLongClient newInstance(int soTimeout) {
        return new HTTPLongClient(soTimeout);
    }

    private HTTPLongClient() {
        init();
    }
    
    private HTTPLongClient(int soTimeout) {
        this.soTimeout = soTimeout;
        init();
    }

    private void init() {
        HttpClientParams params = new HttpClientParams();
        params.setConnectionManagerClass(MultiThreadedHttpConnectionManager.class);
        params.setConnectionManagerTimeout(connectTimeout);
        params.setSoTimeout(soTimeout);
        params.setVersion(HttpVersion.HTTP_1_1);
        httpClient = new HttpClient(params);

        HttpConnectionManagerParams connectManagerParams = new HttpConnectionManagerParams();
        connectManagerParams.setConnectionTimeout(connectTimeout);
        connectManagerParams.setSoTimeout(soTimeout);
        connectManagerParams.setDefaultMaxConnectionsPerHost(maxHostConnections);
        connectManagerParams.setMaxTotalConnections(maxTotalConnections);
        httpClient.getHttpConnectionManager().setParams(connectManagerParams);
        
        if (logger.isInfoEnabled()) {
            logger.info("current properties: \"connectTimeout:"
                    + connectTimeout
                    + "\" , soTimeout:"
                    + soTimeout
                    + "\" , maxConnectionsPerHost:"
                    + maxHostConnections
                    + "\"\n\r"
                    + "if you wanna change the properties and make effective,please call reInit() method after you set new properties!");
        }
    }
    
    public void reInit() {
        instance.init();
    }

    public static HttpClient getHttpClient() {
        return instance.gethttpclient();
    }

    private HttpClient gethttpclient() {
        if (System.currentTimeMillis() - lastClearTime > PERIOD_TIME) {
            releaseIdleConnection();
        }
        httpClient.setState(new HttpState());
        return httpClient;
    }
    
    private synchronized void releaseIdleConnection() {
        lastClearTime = System.currentTimeMillis();
        try {
            httpClient.getHttpConnectionManager().closeIdleConnections(1);
            if (logger.isInfoEnabled()) {
                logger.info("free connections successfully");
            }
        } catch (Exception e) {
            logger.info("", e);
        }
    }
    
    public static String getUrlContent(String httpAddr, boolean isGet, String encodeCharset, Map<String, String> header) {
        return instance.geturlcontent(httpAddr, isGet, null, encodeCharset, header);
    }
    
    public static String getUrlContent(String httpAddr, boolean isGet, Map<String, Object> params,
            String encodeCharset, Map<String, String> header) {
        return instance.geturlcontent(httpAddr, isGet, params, encodeCharset, header);
    }
    
    public String geturlcontent(String httpAddr, boolean isGet, Map<String, Object> params, String encodeCharset,
            Map<String, String> header) {
        HttpMethod httpMethod = prepareHttpMethod(httpAddr, isGet, params, encodeCharset, header);
        String rsp = handler(httpMethod, httpAddr);
        return rsp;
    }
    
    private HttpMethod prepareHttpMethod(String httpAddr, boolean isGet, Map<String, Object> params,
            String encodeCharset, Map<String, String> header) {
        HttpMethod httpMethod = null;
        HttpClient httpClient = gethttpclient();
        if (isGet) {
            httpMethod = new GetMethod(httpAddr);
        } else {
            httpMethod = new PostMethod(httpAddr);
        }
        
        HttpMethodParams httpMethodParams = new HttpMethodParams();
        httpMethodParams.setContentCharset(encodeCharset);
        httpMethodParams.setSoTimeout(httpClient.getParams().getSoTimeout());
        httpMethod.setParams(httpMethodParams);
        
        setDefaultHttpHeader(httpMethod);
        if (header != null) {
            Iterator<String> it = header.keySet().iterator();
            while (it.hasNext()) {
                String key = it.next();
                String val = header.get(key);
                httpMethod.setRequestHeader(key, val);
            }
        }

        if (params != null && !isGet) {
            Iterator<String> it = params.keySet().iterator();
            while (it.hasNext()) {
                String name = it.next();
                Object val = params.get(name);
                if (val instanceof Collection<?>) {
                    Collection<?> collection = (Collection<?>) val;
                    for (Object obj : collection) {
                        ((PostMethod) httpMethod).addParameter(name, String.valueOf(obj));
                    }
                } else {
                    ((PostMethod) httpMethod).addParameter(name, String.valueOf(val));
                }
            }
        }
        
        return httpMethod;
    }
    
    private String handler(HttpMethod httpMethod, String httpAddr) {
        try {
            httpClient.executeMethod(httpMethod);
            int httpStatus = httpMethod.getStatusCode();
            if (httpStatus == 200 || (httpStatus > 300 && httpStatus < 400)) {
                return httpMethod.getResponseBodyAsString();
            } else {
                logger.error("httpStatus : " + httpMethod.getStatusCode() + "," + httpAddr);
                throw new IOException("httpStatus : " + httpMethod.getStatusCode());
            }
        } catch (HttpException e) {
            logger.error("HttpException  : " + httpAddr);
            throw new RuntimeException(e);
        } catch (IOException e) {
            logger.error("IOException", e);
            throw new RuntimeException(e);
        } catch (Exception e) {
            logger.error("OtherException", e);
            return null;
        } finally {
            if (httpMethod != null) {
                httpMethod.releaseConnection();
            }
        }
    }

    private void setDefaultHttpHeader(HttpMethod httpMethod) {
        httpMethod.setRequestHeader("Connection", "keep-alive");
        httpMethod
                .setRequestHeader("User-Agent",
                        "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.12) Gecko/2009070611 Firefox/3.0.12 (.NET CLR3.5.30729)");
        httpMethod.setRequestHeader("refer", "http://dev.apps.com/");
    }
    
    public static String postFile(String httpAddr, String fileName, File file, Map<String, Object> params,
            String encodeCharset) {
        return instance.postfile(httpAddr, fileName, file, params, encodeCharset);
    }

    private String postfile(String httpAddr, String fileName, File file, Map<String, Object> params,
            String encodeCharset) {
        PostMethod httpMethod = new PostMethod(httpAddr);
        try {
            HttpClient httpClient = gethttpclient();
            
            HttpMethodParams httpMethodParams = new HttpMethodParams();
            httpMethodParams.setContentCharset(encodeCharset);
            httpMethodParams.setSoTimeout(httpClient.getParams().getSoTimeout());
            httpMethod.setParams(httpMethodParams);
            
            List<Part> partlist = new LinkedList<Part>();
            if (params != null) {
                Iterator<String> it = params.keySet().iterator();
                while (it.hasNext()) {
                    String name = it.next();
                    Object val = params.get(name);
                    if (val instanceof Collection) {
                        Collection<?> collections = (Collection<?>) val;
                        
                        for (Object object : collections) {
                            partlist.add(new StringPart(name, String.valueOf(object)));
                        }
                    } else {
                        partlist.add(new StringPart(name, String.valueOf(val)));
                    }
                }
            }
            if (file != null) {
                partlist.add(new FilePart(fileName, file));
            }

            Part[] parts = new Part[partlist.size()];
            for (int i = 0; i < partlist.size(); i++) {
                parts[i] = partlist.get(i);
            }

            httpMethod.setRequestEntity(new MultipartRequestEntity(parts, httpMethod.getParams()));
            int httpStatus = httpClient.executeMethod(httpMethod);
            if (httpStatus == 200 || (httpStatus > 300 && httpStatus < 400)) {
                return httpMethod.getResponseBodyAsString();
            } else {
                logger.error("httpStatus:" + httpMethod.getStatusCode() + "," + httpAddr);
                throw new IOException("httpStatus:" + httpMethod.getStatusCode());
            }
        } catch (FileNotFoundException e) {
            logger.error("FileNotFoundException  : " + httpAddr);
            throw new RuntimeException(e);
        } catch (HttpException e) {
            logger.error("HttpException  : " + httpAddr);
            throw new RuntimeException(e);
        } catch (IOException e) {
            logger.error("IOException", e);
            throw new RuntimeException(e);
        } catch (Exception e) {
            logger.error("OtherException", e);
            return null;
        } finally {
            if (httpMethod != null) {
                httpMethod.releaseConnection();
            }
        }
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }
    
    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }
    
    public int getSoTimeout() {
        return soTimeout;
    }
    
    public void setSoTimeout(int soTimeout) {
        this.soTimeout = soTimeout;
    }
    
    public int getMaxHostConnections() {
        return maxHostConnections;
    }
    
    public void setMaxHostConnections(int maxHostConnections) {
        this.maxHostConnections = maxHostConnections;
    }
    
    public int getMaxTotalConnections() {
        return maxTotalConnections;
    }
    
    public void setMaxTotalConnections(int maxTotalConnections) {
        this.maxTotalConnections = maxTotalConnections;
    }

}
